package com.decheng.java8.stream;

import com.decheng.java8.entity.Employee;
import com.decheng.java8.entity.EmployeeData;
import org.junit.Test;

import java.util.*;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * @author 孔德成
 * @slogan 致敬大师，致敬未来的你
 * @date 2021/9/5 3:07 下午
 * @desc Java8 新特性 Stream 流 API 测试
 */

public class StreamApiTest {

    /**
     * stream 流创建方式一：通过集合
     */
    @Test
    public void test1() {
        List<Employee> employees = EmployeeData.getEmployees();
        // default Stream<E> stream() 返回一个顺序流
        Stream<Employee> stream = employees.stream();
        // default Stream<E> parallelStream() 返回并行流
        Stream<Employee> employeeStream = employees.parallelStream();

    }

    /**
     * stream 流创建方式二：通过数组
     */
    @Test
    public void test2() {
        int[] ints = {1, 2, 3, 4, 5, 6};
        // Arrays.stream(T[] array)
        IntStream stream = Arrays.stream(ints);
        Employee e1 = new Employee(1001, "马化腾", 34, 6000.38, true);
        Employee e2 = new Employee(1002, "马云", 12, 9876.12, false);
        Employee[] employees = {e1, e2};
        Stream<Employee> stream1 = Arrays.stream(employees);
    }


    /**
     * stream 流创建方式三：通过 Stream 类的静态方法
     */
    @Test
    public void test3() {
        // public static<T> Stream<T> of(T... values)
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6);
    }

    /**
     * stream 流创建方式四：创建无限流
     */
    @Test
    public void test4() {
        // public static<T> Stream<T> generate(Supplier<T> s)
        // 生成10个随机数
        Stream.generate(Math::random).limit(10).forEach(System.out::println);
        // public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
        // 获取10个偶数
        Stream.iterate(0, e -> e + 2).limit(10).forEach(System.out::println);
    }

    /**
     * stream 中间操作 - 筛选与切片
     */
    @Test
    public void test5() {
        List<Employee> employees = EmployeeData.getEmployees();
        // filter(Predicate p) -  接收 Lambda ， 从流中排除某些元素
        // 练习1：查询工资大于7000的员工
        employees.stream().filter(employee -> employee.getSalary() > 7000).forEach(System.out::println);
        // distinct() - 筛选，通过流所生成元素的 hashCode() 和 equals() 去除重复元素
        employees.add(new Employee(2000, "扎克伯格", 35, 2500.32,true));
        employees.add(new Employee(2000, "扎克伯格", 35, 2500.32,true));
        employees.add(new Employee(2000, "扎克伯格", 35, 2500.32,true));
        employees.add(new Employee(2000, "扎克伯格", 35, 2500.32,true));
        System.out.println("----------------------------------------✂----------------------------------------");
        employees.parallelStream().distinct().forEach(System.out::println);
        // limit(long maxSize) - 截断流，使其元素不超过给定数量
        System.out.println("----------------------------------------✂----------------------------------------");
        employees.stream().limit(5).forEach(System.out::println);
        // skip(long n) - 跳过元素，返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个，则返回一 个空流。与 limit(n) 互补
        System.out.println("----------------------------------------✂----------------------------------------");
        employees.stream().skip(5).forEach(System.out::println);
    }

    /**
     * stream 中间操作：映射
     */
    @Test
    public void test6() {
        // map(Function f) - 接收一个函数作为参数，该函数会被应用到每个元素上，并将其映射成一个新的元素。
        List<String> strings = Arrays.asList("aa", "bb", "cc", "dd");
        strings.stream().map(String::toUpperCase).forEach(System.out::println);
        // 练习：获取员工姓名长度大于3的姓名
        // 员工映射姓名 -> 根据长度过滤姓名
        List<Employee> employees = EmployeeData.getEmployees();
        System.out.println("----------------------------------------✂----------------------------------------");
        employees.stream().map(Employee::getName).filter(s -> s.length() > 3).forEach(System.out::println);
        System.out.println("----------------------------------------✂----------------------------------------");
        // 练习2：计算所有字符
        Stream<Stream<Character>> streamStream = strings.stream().map(StreamApiTest::fromStrToCharacterStream);
        streamStream.forEach(stream -> {
            stream.forEach(System.out::println);
        });
        // flatMap(Function f) - 接收一个函数作为参数，将流中的每个值都换成另一个流，然后把所有流连接成一个流
        // 练习3： 计算所有字符
        System.out.println("----------------------------------------✂----------------------------------------");
        strings.stream().flatMap(StreamApiTest::fromStrToCharacterStream).forEach(System.out::println);
    }

    /**
     * 将字符串映射成字符流
     *
     * @return
     */

    public static Stream<Character> fromStrToCharacterStream(String s) {
        ArrayList<Character> characters = new ArrayList<>();
        for (char c : s.toCharArray()) {
            characters.add(c);
        }
        return characters.stream();
    }

    /**
     * list 集合嵌套 - 理解 map 和 flatMap 的前提
     */
    @Test
    public void test7() {
        List list1 = new ArrayList();
        list1.add(1);
        list1.add(2);
        list1.add(3);

        List list2 = new ArrayList();
        list2.add(4);
        list2.add(5);
        list2.add(6);

//        list1.add(list2);
        System.out.println("list1 = " + list1);
        System.out.println("----------------------------------------✂----------------------------------------");
        list1.addAll(list2);
        System.out.println("list1 = " + list1);
    }


    /**
     * Stream 中间操作 - 排序
     */
    @Test
    public void test9() {
        // sorted() 产生一个新流，其中按自然顺序排序 Comparable
        List<Integer> integers = Arrays.asList(10, 8, 1, 23, 5);
        integers.stream().sorted().forEach(System.out::println);
        System.out.println("----------------------------------------✂----------------------------------------");
        // 对员工进行排序 抛异常：原因是没有实现 Comparable 接口
        List<Employee> employees = EmployeeData.getEmployees();
//        employees.stream().sorted().forEach(System.out::println);
        // sorted(Comparator com) 产生一个新流，其中按比较器顺序排序
        // 练习：对员工按照年龄排序（从小到大），如果年龄相同，则按工资排序（从大到小）
        employees.stream().sorted((e1, e2) -> {
            int compare = Integer.compare(e1.getAge(), e2.getAge());
            if (compare == 0) {
                return -Double.compare(e1.getSalary(), e2.getSalary());
            }
            return compare;
        }).forEach(System.out::println);
        System.out.println("----------------------------------------✂----------------------------------------");
    }

    /**
     * Stream 终止操作 - 匹配与查找
     */
    @Test
    public void test10() {
        List<Employee> employees = EmployeeData.getEmployees();
        // allMatch(Predicate p) 检查是否匹配所有元素
        // 练习1：是否所有员工的年龄都大于18
        boolean b = employees.stream().allMatch(employee -> employee.getAge() > 18);
        System.out.println("b = " + b);
        System.out.println("----------------------------------------✂----------------------------------------");
        // anyMatch(Predicate p) 检查是否至少匹配一个元素
        // 练习2：是否存在员工的工资大于10000，（是否有至少1个员工的工资大于10000）
        boolean b1 = employees.stream().anyMatch(employee -> employee.getSalary() > 10000);
        System.out.println("b1 = " + b1);
        System.out.println("----------------------------------------✂----------------------------------------");
        // noneMatch(Predicate p) 检查是否没有匹配所有元素
        // 练习3：是否没有员工姓雷
        boolean b2 = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
        System.out.println("b2 = " + b2);
        System.out.println("----------------------------------------✂----------------------------------------");
        // findFirst() 返回第一个元素
        Optional<Employee> first = employees.stream().findFirst();
        System.out.println("first = " + first);
        System.out.println("----------------------------------------✂----------------------------------------");
        // findAny() 返回当前流中的任意元素
        Optional<Employee> any = employees.parallelStream().findAny();
        System.out.println("any = " + any);
        System.out.println("----------------------------------------✂----------------------------------------");
        // count() 返回流中元素总数
        // 练习4：返回工资大于5000的员工数量
        long count = employees.stream().filter(employee -> employee.getSalary() > 5000).count();
        System.out.println("count = " + count);
        System.out.println("----------------------------------------✂----------------------------------------");
        // max(Comparator c) 返回流中最大值
        // 练习5：返回工资最高的员工工资
        Optional<Double> max = employees.stream().map(Employee::getSalary).max(Double::compare);
        System.out.println("max = " + max);
        System.out.println("----------------------------------------✂----------------------------------------");
        // min(Comparator c) 返回流中最小值
        // 练习6：返回工资最低的员工
        Optional<Employee> min = employees.stream().min(Comparator.comparingDouble(Employee::getSalary));
        System.out.println("min = " + min);
        System.out.println("----------------------------------------✂----------------------------------------");
        // forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代称为外部迭代。相反，Stream API 使用内部迭代——它帮你把迭代做了)
        employees.stream().forEach(System.out::println);
        System.out.println("----------------------------------------✂----------------------------------------");
        // 集合的默认方法 forEach
        employees.forEach(System.out::println);
        System.out.println("----------------------------------------✂----------------------------------------");
    }

    /**
     * Stream 终止操作 - 归约
     */
    @Test
    public void test11() {
        // reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来，得到一个值。返回 T
        // 练习1：计算 1 到 10 的自然数的和
        Integer reduce = Stream.iterate(1, e -> e + 1).limit(10).reduce(0, Integer::sum);
        System.out.println("reduce = " + reduce);
        System.out.println("----------------------------------------✂----------------------------------------");
        // reduce(BinaryOperator b) 可以将流中元素反复结合起来，得到一个值。返回 Optional<T>
        // 练习2：计算公司所有员工工资的总和
        List<Employee> employees = EmployeeData.getEmployees();
        Optional<Double> reduce1 = employees.stream().map(Employee::getSalary).reduce(Double::sum);
        System.out.println("reduce1 = " + reduce1);
        System.out.println("----------------------------------------✂----------------------------------------");
    }
}
